package semantic;

import java.io.PrintStream;
import java.util.ArrayList;

import astnode.DeclarationNode;
import astnode.FunctionDeclarationNode;

import common.LogPrinter;
import common.PrintVisitor;
import common.TokenType;
/**
 * responsible for check item 15-17
 * @author Yunke Li Boshi Li
 *
 */
public class SemanticAnalyzer {
	 ArrayList<DeclarationNode> roots = null;
	 SymbolTable ST = null;
	 LogPrinter logWriter = null;
	 PrintStream errWriter = null;
	 boolean findErr = false;
	 PrintStream astPrinter = null;
	 ArrayList<String> spellingTable = null;
	 
	public SemanticAnalyzer( ArrayList<DeclarationNode> roots, LogPrinter log, PrintStream errPrinter, PrintStream astPrinter, ArrayList<String> spellingTabel ){
		this.roots = roots;
		this.logWriter = log;
		this.errWriter = errPrinter;
		this.astPrinter = astPrinter;
		this.spellingTable = spellingTabel;		
	}
	
	/**
	 * analyze file and product codes
	 */
	public void analyse(){
		//initial ST by the size of spelling table
		ST = new SymbolTable( spellingTable.size() , logWriter, errWriter, spellingTable);
		initialScan();
		traversal();
		checkMain();
	}
	
	/**
	 * print AST to ast printer
	 */
	public void printAST(){
		for( int i=0 ; i<roots.size(); i++ ){
			roots.get(i).accept( new PrintVisitor(0, astPrinter , spellingTable ));
		}
	}
	
	/**
	 * @return if semantic analyzer found errors
	 */
	public boolean containErr(){
		return findErr || SemanticVisitor.containErr() || ST.isContainsError();
	}
	
	private void initialScan(){		
		for(int i=0; i<roots.size(); i++ ){
			DeclarationNode dec = roots.get(i);
			
			if( dec instanceof FunctionDeclarationNode ){
				String lexeme = spellingTable.get( dec.getIdentifier() );
				if( lexeme.equals("writeint") || lexeme.equals("readint") || lexeme.equals("readbool") || lexeme.equals("writebool") ){
					printErr( dec.getLineNum(), String.format("%s has been declared as environment function", lexeme) );
				}else{
					ST.createEntry( 0, dec , dec.getIdentifier(),  roots.get(i).getLineNum() );
				}				
			}
		}
	}
	
	private void traversal(){		
		for(int i=0; i<roots.size(); i++ ){
			DeclarationNode dec = roots.get(i);
			if( dec instanceof FunctionDeclarationNode ){
				dec.accept( new SemanticVisitor(ST, logWriter, errWriter, spellingTable ) );
			}else{
				//dec is variable declaration, create entry in ST immediately.
				ST.createEntry( 0, dec, dec.getIdentifier() ,  roots.get(i).getLineNum() );
			}
		}
	}
	
	private void checkMain(){
		//check main function
		int lastIndex = roots.get( roots.size()-1).getIdentifier();
		DeclarationNode lastDec =   roots.get( roots.size()-1 );
		//check item:17, 15
		//check if last function declaration is 'main'
		if( !spellingTable.get(lastIndex).equals("main") || ! ( lastDec instanceof FunctionDeclarationNode )  ){
			logWriter.println("Last declaration must be 'main' function declaration");
			printErr( lastDec.getLineNum(), "Last declaration must be 'main' function declaration");
		}else{
			//check item:16 
			//check if function type is INT
			if( lastDec.getType()!=TokenType.INT ){
				logWriter.println("'main' function must be declared as INT type");
				printErr( lastDec.getLineNum(), "'main' function must be declared as INT type");
			}
			if( (( FunctionDeclarationNode)lastDec ).getParameterList().size()!=0 ){
				logWriter.println("'main' function can not have parameters");
				printErr(lastDec.getLineNum(), "'main' function can not have parameters");
			}
		}
	}
	
	private void printErr( int lineNum, String err ){
		findErr  = true;
		errWriter.println(String.format("Line %d: %s", lineNum, err));
	}
}
